
// console.log(null == 0)

/**
 * 有两种类型：基本类型和引用类型
 * 基本类型：number, string, boolean, null , undefined
 * 引用类型：object, array, function date map set RegExp
 */

// Symbol 符号的用途是保证对象的属性名是唯一的，可以保证对象属性名不冲突
// let genericSymbol = Symbol();

// let genericSymbol2 = Symbol();
// console.log(genericSymbol)
// console.log(typeof genericSymbol);

// let colors = ['red', 'blue'];
// console.log(colors);

// colors.push('green');

// console.log(colors);

// colors.unshift('yellow');

// console.log(colors);

// colors.splice(4, 0, 'black');
// console.log(colors);



// let str = "12+34+56";
// let result = str.split("+");
// console.log(result);


// let obj = {valueOf: function() {return 10}}
// let other = {}
// console.log(other == 10)


/**
 * 等于号运算符
 *  两个都为简单类型，字符串和布尔值都会转为数值，再比较
 *  简单类型与引用类型比较，引用类型转换为原始类型的值，再比较
 *  两个引用类型比较，则比较它们是否指向同一个对象
 *  null和undefined比较 返回true
 *  NaN和任何值比较 返回false
 * 
 */

/**
 * 全等运算符
 *  全等运算符由3个等于号表示，只有两个操作数在不转换的前提下相等，才返回true
 *  即类型相同，值也相同
 */

/**
 *  等于运算符 与 全等运算符 区别
 *  相等操作符会做类型转换，再进行值的比较，全等运算符不会做类型转换，直接比较
 * 
 */


/**
 * 深拷贝 与 浅拷贝的区别
 */

/**
 * 浅拷贝，指的是创建新的数据，这个数据有着原始数据值的一份精确拷贝
 * 如果属性是基本类型，拷贝的就是基本类型的值。如果属性是引用类型，拷贝的就是内存地址
 * 即浅拷贝就是拷贝一层，深层次的引用类型则共享内存地址
 * @param obj 
 * @returns 
 */
// function shallowClone(obj: any) {
//     const newObj = {} as any;
//     for(let prop in obj) {
//         if(obj.hasOwnProperty(prop)) {
//             newObj[prop] = obj[prop];
//         }
//     }
//     return newObj;
// }

// console.log(typeof shallowClone);  // function
/**
 * 在JS中，存在浅拷贝的现象有：
 * Object.assign()
 * Array.prototype.slice() ,Array.prototype.concat()
 * 使用扩展运算符实现的复制
 */


/**
 * 深拷贝开辟一个新的栈，两个对象属性完全相同，但是对应两个不同的地址，修改一个对象的属性，不会改变另一个对象的属性
 * 常见的深拷贝方式有：
 *  _.cloneDeep
 *  Jq的extend
 *  JSON.parse(JSON.stringify())
 *  手写循环递归
 */

// function deepClone(obj: any, hash = new WeakMap()) {

//     // 如果是null或者undefined，直接返回, 就不进行拷贝操作
//     if(obj == null) {
//         return obj;
//     }

//     if(obj instanceof Date) {
//         return new Date(obj);
//     }
//     if(obj instanceof RegExp) {
//         return new RegExp(obj);
//     }
//     // 可能是对象或者普通的值， 如果是函数的话是不需要深拷贝
//     if(typeof(obj) !== 'object') {
//         return obj;
//     }

//     if(hash.get(obj)) {
//         return hash.get(obj);
//     }

//     let cloneObj = new obj.constructor();
//     hash.set(obj, cloneObj);
//     for(let prop in obj) {
//         if(obj.hasOwnProperty(prop)) {
//             // 实现一个深拷贝
//             cloneObj[prop] = deepClone(obj[prop], hash);
//         }
//     }
//     return cloneObj;
// }

// let obj = {
//     address: {
//         province: 'beijing'
//     },
//     add: ()=>{
//         console.log('add');
//     }
// }

// deepClone(obj);

/**
 * 深拷贝与浅拷贝的区别
 *  前提为拷贝类型为引用类型的情况下
 *   浅拷贝是拷贝一层，属性为对象时，浅拷贝是复制，两个对象指向同一个地址
 *   深拷贝是递归拷贝深层次，属性为对象时，深拷贝是新开栈，两个对象指向不同的地址
 */


/**
 * 闭包 是什么
 *  一个函数和对其周围状态的引用捆绑在一起（或者说函数被引用包围），这样的组合就是闭包（closure）
 *  也就是说，闭包让你可以在一个内层函数中访问到其外层函数的作用域
 * 
 * 闭包的使用场景
 *  任何闭包的使用场景都离不开这两点
 *    创建私有变量
 *    延长变量的声明周期
 */

//  function fontSet() {
//     function makeSizer(size) {
//         return function() {
//             document.body.style.fontSize = size + 'px';
//         }
//     }

//     let size12 = makeSizer(12);
//     let size14 = makeSizer(14);
//     let size16 = makeSizer(16);

//  }

 /**
  * 柯里化函数
  *   目的把在于避免频繁调用具有相同参数函数的同时，又能够轻松的重用
  * 
  */
 // 实现一个柯里化函数
//  function curry(fn: Function, args: any[]) {
//     let len = fn.length;
//     args = args || [];
//     return function(...innerArgs: any[]) {
//         // let innerArgs = Array.prototype.slice.call(arguments);
//         if(innerArgs.length + args.length >= len) {
//             return fn.apply(null, args.concat(innerArgs));
//         } else {
//             return curry(fn, args.concat(innerArgs))
//         }
//     }
//  }

//  function add(num1: any, num2: any, num3: any, num4: any, num5:any) {
//      let rs =  num1 + num2 + num3 + num4 + num5;
//      console.log(rs);
//      return rs;
//  }

//   let curriedAdd = curry(add, 1);
//   curriedAdd(2)(3)(4)(5);
  
 /**
  * 使用闭包模拟私有变量
  */

//  let Counter = (function() {
//     let privateNum = 0;

//     function changeBy(val) {
//         privateNum += val;
//     }

//     return {
//         get: function() {
//             return privateNum;
//         },
//         add: changeBy,
//         minus: function(val) {
//             changeBy(-val);
//         }
//     }
//  })()

/**
 * 你对作用域链的理解
 *  作用域，即变量与函数的可访问范围，在JavaScript中，函数的作用域在定义时决定，而不是在运行时
 *  换句话说，作用域决定了代码区块中变量和其他资源的可见性
 * 
 * 我们一般将作用域分成：
 *  全局作用域
 *  函数作用域
 *  块级作用域
 *  */ 




/**
 * 
 * javascript原型、原型链？有什么特点？
 * 
 *  原型：
 *    JavaScript常被描述为一种基于原型的语言---每个对象拥有一个原型对象
 *    当视图访问一个对象的属性时，它不仅在该对象上搜寻，还会搜寻该对象的原型，以及该对象的原型的原型，
 * 依次层层向上搜索，直到找到一个名字匹配的属性或到达原型链的末尾
 * 
 *   准确的说，这些属性和方法定义在Object的构造器函数之上的protptype属性上，而非实例对象本身
 * 
 * 
 */


// Function.prototype.myApply = function(content, argsArray){
//     content = content || window;
//     argsArray = argsArray || [];
//     let fn = Symbol();
//     content[fn] = this;

//     let result;
//     if(argsArray.length) {
//         result = content[fn](...argsArray);
//     } else {
//         result = content[fn]();
//     }
//     delete content[fn];
//     return result;
// }


// Function.prototype.myBind = function(content, ...bindArgs){
//     const self = this;

//     return function(...callArgs){
        
//         return self.apply(content, [...bindArgs,...callArgs]);
//     }
// }


/**
 * 如何实现继承？
 *  常见的继承方式： 原型链继承、借用构造函数继承、组合继承、原型式继承、寄生式继承
 */

// 原型链继承
/**
 * 看似没问题，实际存在潜在问题
 *  改变c1的play属性，会发现c2的play属性也跟着改变了，这是因为两个实例使用的是同一个原型对象，
 * 内存空间是共享的
 */
// function Parent() {
//     this.name = 'parent1';
//     this.play = [1, 2, 3]
//   }
//   function Child() {
//     this.type = 'child2';
//   }
//   Child.prototype = new Parent();
//   let p = new Parent();
//   let c = new Child();
//   let c2 = new Child();
//   console.log(p)
//   console.log(c)

/**
 * 构造函数继承
 *  借助call方法，调用Parent函数
 *  父类原型对象中一旦存在父类之前自己定义的方法，那么子类将无法继承这些方法
 * 
 *  相比第一种原型链继承方式，父类的引用属性不会被共享，优化了第一种继承方式的弊端，但是只能继承父类的实例属性和方法，
 * 不能继承原型属性或者方法
 */
// function Parent(){
//   this.name = 'parent1';
//   this.add = function(num1, num2){
//     return num1 + num2;
//   }
// }

// Parent.prototype.getName = function () {
//   return this.name;
// }

// function Child(){
//   Parent.call(this);
//   this.type = 'child'
// }

// let p = new Parent();
// let child = new Child();

// console.log(p)
// console.log(child)


/**
 * 谈谈this对象的理解
 *  根据不同的使用场合，this有不同的值，主要分为下面几种情况：
 *   全局作用域中，this指向window对象
 *   函数中，this指向调用该函数的对象
 *   
 *   默认绑定、隐士绑定、显式绑定、new绑定
 */

// 默认绑定 全局
// function fn() {
//   this // 修改this，运行后会报错
//   console.log(this);
//   console.log(this.a);
// }

// fn();

// 隐士绑定
// var o = {
//   a:10,
//   b:{
//       fn:function(){
//           console.log(this.a); //undefined
//       }
//   }
// }
// o.b.fn();


// var o = {
//   a:10,
//   b:{
//       a:12,
//       fn:function(){
//           console.log(this.a); //undefined
//           console.log(this); //window
//       }
//   }
// }
// var j = o.b.fn;
// j();


// 显式绑定
/**
 * applay、call、bind是函数的一个方法，作用是改变函数的调用对象。它的第一个参数就表示改变后
 * 的调用这个函数的对象。因此，这时this指的就是这第一个参数
 * 
 */

/**
 *  箭头函数
 * 
 */


// let a = function(){
//   console.log(this);
//   console.log('a');
// }

// let b = ()=>{
//   console.log(this);
//   console.log('b');
// }

// a();
// b();


/**
 * js中执行上下文和执行栈是什么
 * 
 * 执行上下文：
 *  执行上下文是js中一个重要的概念，它是一个抽象的概念，可以理解为js代码的运行环境
 *  执行上下文是一种对javaScript代码执行环境的抽象概念，也就是说只要有javaScript代码运行，
 * 那么它就一定是运行在执行上下文中
 * 
 * 
 * 生命周期：
 *  执行上下文的生命周期包括三个阶段：创建阶段、执行阶段、销毁阶段
 * 
 * 
 * 执行栈：
 *  也叫做调用栈，具有LIFO（后进先出）的结构，用来存储在代码执行期间创建的所有执行上下文
 * 
 *   1、创建全局上下文并压入执行栈
 *   2、first函数被调用，创建函数执行上下文并压入栈
 *   3、执行first函数过程遇到second函数，再创建一个函数执行上下文并压入栈
 *   4、second函数执行完毕，对应的函数执行上下文被推出执行栈，执行下一个执行上下文first函数
 *   5、first函数执行完毕，对应的函数执行上下文被推出执行栈，然后执行全局上下文
 *   6、所有代码执行完毕，全局上下文也会被推出执行栈，程序结束
 */


/**
 * 说说JavaScript中的事件模型
 *   事件模型可以分为三种：
 *     1、原始事件模型（DOM0级）
 *     2、标准事件模型（DOM2级）
 *     3、IE事件模型（基本不用）
 */


// let a = ()=>{}

// let b = ()=>{}

// console.log( b instanceof Function)


/**
 * 说说 typeof 与 instanceof 的区别
 *  typeof 操作符返回一个字符串，表示未经计算的操作数的类型
 *  instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性
 *  
 */

// function myInstanceof(left, right) {
//   let proto = Object.getPrototypeOf(left);
//   while (true) {
//     if (proto === null) return false;
//     if (proto === right.prototype) return true;
//     proto = Object.getPrototypeOf(proto);
//   }
// }

/**
 *  区别：
 *   typeof 与 instanceof 都是判断数据类型的方法
 *   
 *   typeof 会返回一个变量的基本类型，而 instanceof 返回的是一个布尔值
 *    instanceof 可以准确地判断发咋引用数据类型，但是不能正确判断基础数据类型
 *    而 typeof 也存在弊端，它虽然可以判断基础数据类型， 但是引用数据类型无法判断
 * 
 *   如果需要通用检测数据类型，可以采用 Object.prototype.toString.call(obj), 调用该方法，
 * 统一返回格式 “[object xxx]” 的字符串
 */

//  function getType(obj) {
//   let type = typeof obj;
//    if(type !== 'object'){
//      return type;
//    }
//    return Object.prototype.toString.call(obj).slice(8,-1);
//  }

/**
 * 说说你对事件循环的理解
 * 
 *  
 */

// console.log(1)  // 同步任务，主线程直接执行

// setTimeout(()=>{   // 异步任务，放入宏任务队列
//     console.log(2)
// }, 0)

// new Promise((resolve, reject)=>{ // 异步任务，放入微任务队列
//     console.log('new Promise')
//     resolve()
// }).then(()=>{  // 微任务，放入微任务队列
//     console.log('then')
// })

// console.log(3)  // 同步任务，主线程直接执行


/**
 * 说说你对递归的理解
 *  是指在函数的定义中使用函数自身的方法
 *  在函数内部，可以调用其他函数。如果一个函数在内部调用自身，这个函数就是递归函数
 *  其核心思想是把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解
 * 
 * 一般来说，递归需要有边界条件、递归前进阶段和递归返回阶段。当边界条件不满足时，递归前进；
 * 当边界条件满足时，递归返回
 */


// function recursion(n){
//     if(n === 1) return 1;
//     console.log(`${n}: ${n} + recursion(${n}-1)`);
//     return n + recursion(n-1);
// }

// console.log(recursion(5));


// 阶乘
// function factorial(n) {
//     if (n === 1) return 1;
//     return n * factorial(n - 1);
// }

// function factorial2(n, result = 1) {
//   if (n === 1) return result;
//   return factorial2(n - 1, n * result);
// }


// 斐波那契数列 英文：Fibonacci sequence
// function Fibonacci(n) {
//     if (n === 1 || n === 2) return 1;
//     return Fibonacci(n - 1) + Fibonacci(n - 2);
// }


/**
 * 数组扁平化
 */

// let a = [1,2,3, [1,2,3, [1,2,3]]]
// 变成
// let a = [1,2,3,1,2,3,1,2,3]
// 具体实现
// function flat(arr = [], result = []) {
//     arr.forEach(v => {
//         if(Array.isArray(v)) {
//             result = result.concat(flat(v, []))
//         }else {
//             result.push(v)
//         }
//     })
//     return result
// }

// console.log(flat(a))

// // 宏任务、微任务执行顺序

// async function async1() {
//   console.log('async1')
//   await async2()
//   console.log('async1 end')
// }

// async function async2() {
//   console.log('async2')
// }

// console.log('script start')
// setTimeout(function() {
//   console.log('setTimeout0')
//   setTimeout(function() {
//       console.log('setTimeout1')
//   }, 0)

//   setImmediate(function() {
//       console.log('setImmediate')
//   }, 0)
// }, 0)

// async1()

// process.nextTick(function() {
//   console.log('nextTick')
// })

// new Promise(function(resolve) {
//   console.log('promise1')
//   resolve()
//   console.log('promise2')
// }).then(function() {
//   console.log("promise.then")
// })

// console.log('script end')


// 阻塞 与 非阻塞

// let glob = require('glob')
// console.log(__dirname)
// glob(__dirname+'**/*.js', function(err, files) {
//     console.log(files)
// })



